"""
Copyright 2009, Thomas Dejanovic, Jay Shurtz.
 
This is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of
the License, or (at your option) any later version.

This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this software; if not, write to the Free
Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.

Generic console library. 
"""

HEAD_URL = "$HeadURL: http://hatch.googlecode.com/svn/tags/taggle_release_2.2/hatch_interface/hatch_console.py $"
REVISION = "$Revision: 734 $"
AUTHOR = "$Author: jayshurtz $"
DATE = "$Date: 2010-10-08 04:33:45 +0000 (Fri, 08 Oct 2010) $"
ID = "$Id: hatch_console.py 734 2010-10-08 04:33:45Z jayshurtz $"
version = " ".join(ID.split()[2:4])


import optparse
import logging
import time


import to_hatch_structure
del to_hatch_structure
import hatch_custom_tabcomplete
from hatch_custom_history import hist, setupSimpleHistory, DEFAULT_LENGTH, historyFileName
import hatch_easy_logger


from os import path as ospath   
fileName = ospath.splitext(ospath.basename(__file__))[0] # Get the file name without the extension.


# Define option defaults.
optionDefaults = {}
optionDefaults["historyFile"] = historyFileName("%f")   # History file defaults.
optionDefaults["historyLength"] = DEFAULT_LENGTH
optionDefaults['loggingLevel'] = 'info'
optionDefaults['logFile'] = ospath.join("%f" + '.log')  # File name (%f) and date.
optionDefaults['logFileFormat'] = "%(asctime)s  %(levelname)s  %(name)s  %(message)r"
optionDefaults['logFileFormatTime'] = "%Y-%m-%d %H:%M:%S"   # None


# Option parser functions.
def addScriptGroup(parser, optionDefaults=optionDefaults, defaultString="Default: "):
    """ """
    scriptGroup = optparse.OptionGroup(parser, "Script Options", "")
    historyFile = optparse.Option("--history-file", dest="historyFile", metavar="FILE")
    historyFile.help = defaultString + repr(optionDefaults["historyFile"])
    scriptGroup.add_option(historyFile)
    historyLength = optparse.Option("--history-length", dest="historyLength", type="int", metavar="INT")
    historyLength.help = defaultString + repr(optionDefaults["historyLength"])
    scriptGroup.add_option(historyLength)
    loggingLevelChoices = hatch_easy_logger.loggingLevels.keys()  #['debug','info','warning','error']
    loggingLevel = optparse.Option("-l", "--log-level", dest="loggingLevel", metavar="STR")
    loggingLevel.type, loggingLevel.choices = "choice", loggingLevelChoices
    loggingLevel.help = "Set the console logging level."
    loggingLevel.help += " Options are: " + repr(loggingLevelChoices) + ". Default: " + repr(optionDefaults["loggingLevel"])
    scriptGroup.add_option(loggingLevel)
    logFile = optparse.Option("-f", "--log-file", dest="logFile", metavar="FILE")
    logFile.help = "Log file and path. Use 'None' for no log file. " + defaultString + repr(optionDefaults["logFile"])
    scriptGroup.add_option(logFile)
    logFileFormat = optparse.Option("--log-format", dest="logFileFormat", metavar="STR")
    logFileFormat.help = "Log file format. See strftime() and python logging module for details. "
    logFileFormat.help += defaultString + repr(optionDefaults["logFileFormat"])
    scriptGroup.add_option(logFileFormat)
    logFileFormatTime = optparse.Option("--log-time", dest="logFileFormatTime", metavar="STR")
    logFileFormatTime.help = "Log file time/date format. See strftime() and python logging module for details. "
    logFileFormatTime.help += defaultString + repr(optionDefaults["logFileFormatTime"])
    scriptGroup.add_option(logFileFormatTime)
    parser.add_option_group(scriptGroup)


def getCommandLineOptions(parser):
    """ Get command line options.
        'parser' is optparse.parser object returned by getOptionParser().
        'getOptionDefaults' is the callback that gets option defaults.
        Returns a dictionary of options.
    """
    (options, args) = parser.parse_args()
    if len(args) > 0:
        raise SystemExit, "All command line inputs are options."
    del parser
    options = options.__dict__
    return options


def setDefaults(dictTo, dictFrom):
    """ Replace values in dictTo that are 'None' (or do not exist)
        with values from dictFrom.
        optparse options (dictTo) default to 'None', which is why a seperate
        defaults dictionary (dictFrom) is used.
    """
    for key, value in dictFrom.iteritems():
        if dictTo.get(key, None) is None:
            dictTo[key] = value
    return dictTo


# User interface functions.
def getLogFileHandler(options, fileName=fileName, mode='a', level=logging.DEBUG):
    """ Return a logging fileHandler object from options.
        'fileName' is a string that replaces '%f' in options['logFile'].
        'mode' is 'a' for append, 'w' for write, etc.
        'level' is the file logging level.
    """
    fileHandler = None
    if options.get("logFile", 'None') != 'None':
        logFileName = options['logFile'].replace("%f", fileName)
        logFileName = time.strftime(logFileName)
        fileHandler = logging.FileHandler(logFileName, mode)
        fileHandler.setFormatter(logging.Formatter(options['logFileFormat'], options['logFileFormatTime']))
        fileHandler.setLevel(level)
    return fileHandler


def getLogConsoleHandler(options):
    """ Return a logging streamHandler object from options.
    """
    consoleHandler = logging.StreamHandler()    # Logs to console (not stdout!)
    hatch_easy_logger.setLogLevel(options['loggingLevel'], consoleHandler)
    return consoleHandler


# Easy setup functions for scripts that import this file.
def setupHistory(options, fileName=fileName):
    """ Setup history from options.
    """
    historyFileName = options['historyFile'].replace("%f", fileName)
    setupSimpleHistory(historyFileName, options['historyLength'])


def setupLogging(options, logger, fileName=fileName):
    """ Use the default logging setup.
    """
    consoleHandler = getLogConsoleHandler(options)
    logger.addHandler(consoleHandler)
    fileHandler = getLogFileHandler(options, fileName)
    if fileHandler is not None:
        logger.addHandler(fileHandler)
    return consoleHandler, fileHandler


def setupConsole(options, logger, fileName=fileName, formatter=None):
    """ Use the default console setup.
        Sets up history & logging.
    """
    setupHistory(options, fileName)
    consoleHandler, fileHandler = setupLogging(options, logger, fileName)
    logHandlers = [consoleHandler]
    if fileHandler is not None:
        if formatter is not None:
            formatter = baseStationLogFormatter(options['logFileFormat'], options['logFileFormatTime'])
            fileHandler.setFormatter(formatter)
        logHandlers.append(fileHandler)
    return logHandlers


# Special logger formatting.
class baseStationLogFormatter(logging.Formatter):
    """ Perform additional log record formatting operations.
        Calling "logging.Formatter.format(self, record)" uses record.__dict__
        to set items in self._fmt:  "s = self._fmt % record.__dict__"
        So '%(levelname)s' in self._fmt becomes record.levelname, or 'INFO'.
        New keys may be added by adding class attributes:  "record.xyz = 'foo'"
        So '%(xyz)s' in self._fmt becomes record.xyz, or 'foo'
    """
    dirfile = ospath.sep.join(ospath.abspath(__file__).split(ospath.sep)[-2:])  # Get parent_directory/filename.
    levelNames = {'DEBUG': 'D410', 'INFO': 'S420', 'WARNING': 'W430', 'ERROR': 'E440', 'CRITICAL': 'E450'}

    def format(self, record):
        """ Replace <filename> it with <parent_directory/filename>
            Replace 'DEBUG', 'INFO', 'WARNING', etc with 'D410', 'S420', 'W430', etc.
            Remove raw string quotes from <message>.
        """
        record.levelname = self.levelNames[record.levelname] # These codes are hard-wired. ex: 'DEBUG' -> 'D410'.
        record.filename = self.dirfile
        formatted = logging.Formatter.format(self, record)
        formatted = formatted.replace("%r" % record.message, ("%r" % record.message).strip("'").strip('"'))
        return formatted


# Options classes.
class ClassOptions(object):
    """ Super class for making "using command line arguments as parameters in 
        the creation of another class instance" easy to implement:
          this -> default options dict
          this -> optparse option groups
          this -> class instance
    """
    optionDefaults = {}
    # XXX Add defaults here as [key] = value.

    def __init__(self, name='', shortOptions={}, options={}, delimiter="_"):
        """ Example: 
                instance = ClassOptions('Bob', {'port': '-o'}, {'port': '/dev/ttyUSB1'})
        """
        self.name = name
        self.shortOptions = shortOptions
        self.delimiter = delimiter
        _options = self.optionDefaults.copy()
        _options.update(options)
        self.options = _options

    def addName(self, key):
        """ Add the 'name' argument to make dictionary keys unique.
        """
        return (self.name + self.delimiter) + key

    def stripName(self, key):
        """ Remove the 'name' argument to make dictionary keys generic.
        """
        return key.replace(self.name + self.delimiter, "")

    def getOpt(self, opt):
        """ Convert generic long options into unique long options using self.name.
            Ex: 'xml' -> '--bob-xml'
        """
        return ("--" + self.name.lower() + "-" + opt).replace('_','-')

    def optionsToDict(self):
        """ Return a dictionary of options with unique keys.
        """
        _dict = {}
        for key, value in self.options.iteritems():
            _dict[self.addName(key)] = value
        return _dict

    def dictToOptions(self, _dict):
        """ Convert dictionary of unique options to generic parameters for this instance.
        """
        for key, value in _dict.iteritems():
            self.options[self.stripName(key)] = value

    def addOptionGroups(self, parser, defaultString="Default: "):
        """ Add options to parser.
        """
        raise NotImplementedError

    def create(self, options, logHandlers=[]):
        """ Return a class instance.
            'options' should be a dictionary returned by optparse, ie:
              >>> (options, args) = parser.parse_args()
              >>> options = options.__dict__
        """
        raise NotImplementedError


def getOptionParser(optionDefaults, optsList=[], defaultString="Default: "):
    """ """
    parser = optparse.OptionParser(epilog="\n\n", version="%prog " + str(version))
    parser.usage = "%prog [options]"
    parser.description = "Console interface."
    parser.formatter = optparse.TitledHelpFormatter() #IndentedHelpFormatter()
    addScriptGroup(parser, optionDefaults, defaultString)
    for opts in optsList:
        opts.addOptionGroups(parser)  # Add options.
    return parser


# Main function.
if __name__ == "__main__":

    import traceback

    try:
        # Set-up a logger for this script, default to debug logging.
        logger = logging.getLogger(fileName)
        logger.setLevel(logging.DEBUG)

        # Get option parser.
        parser = getOptionParser(optionDefaults, [])

        # Get command line options & defaults.
        options = getCommandLineOptions(parser)
        options = setDefaults(options, optionDefaults)

        # Setup console.
        if True:
            logHandlers = setupConsole(options, logger, fileName)
        else:   # Setup logging with a special log formatter.
            logHandlers = setupConsole(options, logger, fileName, \
                baseStationLogFormatter(options['logFileFormat'], options['logFileFormatTime']))

    except SystemExit, e:
        pass
    except Exception, e:    # Log and raise all errors.
        logger.error(traceback.format_exc())
        raise
    else:
        logger.info("Done.")


